﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using aoprinter.Model;
using aoprinter.Draw;

namespace aoprinter.Core
{
    static class TableOper
    {
        static private int MaxCellCount(this ModTable table)
        {
            int maxCount = 0;
            switch (table.TableBindType)
            {
                case EnmTableBindType.Row:
                    for (int i = 0; i < table.Items.Count; i++)
                    {
                        var iCellNum = table.Items[i].Cells.Select(p => p.ColumnSpan).Sum();
                        if (iCellNum > maxCount)
                        {
                            maxCount = iCellNum;
                        }
                    }
                    break;
                case EnmTableBindType.Column:
                    for (int i = 0; i < table.Items.Count; i++)
                    {
                        var iCellNum = table.Items[i].Cells.Select(p => p.RowSpan).Sum();
                        if (iCellNum > maxCount)
                        {
                            maxCount = iCellNum;
                        }
                    }
                    break;
            }
            return maxCount;
        }

        static private void AutoSizeItems(this ModTable table, SizeF size)
        {
            float useSize = table.TableBindType == EnmTableBindType.Row ? table.Size.Height : table.Size.Width;
            bool ifAutoSize = useSize == (table.TableBindType == EnmTableBindType.Row ? ModTable.AutoSize.Height : ModTable.AutoSize.Width);
            var autoSizeItem = table.Items.FindAll(p => p.Size <= 0);
            if (autoSizeItem != null && autoSizeItem.Count > 0)
            {
                if (ifAutoSize)
                {
                    useSize = table.TableBindType == EnmTableBindType.Row ? size.Height : size.Width;
                }
                float preSize = (useSize - table.Items.Where(p => p.Size > 0).Sum(p => p.Size)) / autoSizeItem.Count;
                preSize = preSize < 0 ? 0 : preSize;
                autoSizeItem.ForEach(p => p.Size = preSize);
            }

            //set table size with item
            if (ifAutoSize)
            {
                float totalItemSize = table.Items.Sum(p => p.Size);
                table.Size = table.TableBindType == EnmTableBindType.Row ? new SizeF(table.Size.Width, totalItemSize)
                    : new SizeF(Math.Min(totalItemSize, size.Width), table.Size.Height);
            }
        }

        static private float[] CellUnitSize(this ModTable table, SizeF size)
        {
            //find the most fitable cell size array
            int maxCellCount = table.MaxCellCount();
            float[] cellSizes = new float[maxCellCount];
            for (int i = 0; i < maxCellCount; i++)
            {
                cellSizes[i] = -1;
            }
            int cellIndex = 0;
            for (int i = 0; i < table.Items.Count; i++)
            {
                cellIndex = 0;
                for (int j = 0; j < table.Items[i].Cells.Count; j++)
                {
                    switch (table.TableBindType)
                    {
                        case EnmTableBindType.Row:
                            {
                                if (table.Items[i].Cells[j].ColumnSpan == 1
                                    && table.Items[i].Cells[j].Size.Width > cellSizes[cellIndex])
                                {
                                    cellSizes[cellIndex] = table.Items[i].Cells[j].Size.Width;
                                }
                                cellIndex += table.Items[i].Cells[j].ColumnSpan;
                            }
                            break;
                        case EnmTableBindType.Column:
                            {
                                if (table.Items[i].Cells[j].RowSpan == 1
                                    && table.Items[i].Cells[j].Size.Height > cellSizes[cellIndex])
                                {
                                    cellSizes[cellIndex] = table.Items[i].Cells[j].Size.Height;
                                }
                                cellIndex += table.Items[i].Cells[j].RowSpan;
                            }
                            break;
                    }
                }
            }

            //deal the cell size array with table size
            float sizeSum = cellSizes.Sum(p => p < 0 ? 0 : p);
            float useSize = table.TableBindType == EnmTableBindType.Row ? table.Size.Width : table.Size.Height;
            bool ifAutoSize = useSize == (table.TableBindType == EnmTableBindType.Row ? ModTable.AutoSize.Width : ModTable.AutoSize.Height);
            float scaleFactor = 1;
            if (ifAutoSize)
            {
                useSize = table.TableBindType == EnmTableBindType.Row ? Math.Max(sizeSum, size.Width) : size.Height;
            }
            if (sizeSum > useSize && sizeSum > 0)
            {
                scaleFactor = useSize * 1.0f / sizeSum;
                for (int i = 0; i < cellSizes.Length; i++)
                {
                    if (cellSizes[i] > 0)
                    {
                        cellSizes[i] = Convert.ToInt32(scaleFactor * cellSizes[i]);
                    }
                    else
                    {
                        cellSizes[i] = 0;
                    }
                }
            }
            else
            {
                int autoSizeCount = cellSizes.Count(p => p <= 0);
                if (autoSizeCount > 0)
                {
                    float autoCellSize = (useSize - sizeSum) / autoSizeCount;
                    for (int i = 0; i < cellSizes.Length; i++)
                    {
                        if (cellSizes[i] <= 0)
                        {
                            cellSizes[i] = autoCellSize;
                        }
                    }
                }
            }
            return cellSizes;
        }

        static public void AutoSizeLocation(this ModTable table, SizeF size, Drawer drawer)
        {
            table.AutoSizeItems(size);
            float[] itemSizes = table.Items.Select(p => p.Size).ToArray();
            float[] cellSizes = table.CellUnitSize(size);
            int[] cellIndexs = new int[table.Items.Count];
            float totalCellSize = cellSizes.Sum();

            for (int i = 0; i < table.Items.Count; i++)
            {
                for (int j = 0; j < table.Items[i].Cells.Count; j++)
                {
                    if (table.Items[i].Cells[j].Position == EnmPosition.Suspend)
                    {
                        table.Items[i].Cells[j].Position = table.Position;
                    }
                    switch (table.TableBindType)
                    {
                        case EnmTableBindType.Row:
                            {
                                table.Items[i].Cells[j].Size = new SizeF(
                                    GetSum(cellSizes, cellIndexs[i], cellIndexs[i] + table.Items[i].Cells[j].ColumnSpan - 1),
                                    GetSum(itemSizes, i, i + table.Items[i].Cells[j].RowSpan - 1));
                                table.Items[i].Cells[j].Location = new PointF(
                                    GetSum(cellSizes, 0, cellIndexs[i] - 1),
                                    GetSum(itemSizes, 0, i - 1));
                                table.Items[i].Cells[j].Blocks.ForEach(p => p.InheritLocation(table.Position));
                                table.Items[i].Cells[j].Blocks.ForEach(p => p.AutoSizeLocation(table.Items[i].Cells[j].Size, drawer));
                                cellIndexs[i] += table.Items[i].Cells[j].ColumnSpan;
                                for (int k = 1; k < table.Items[i].Cells[j].RowSpan; k++)
                                {
                                    if (i + k < cellIndexs.Length)
                                    {
                                        cellIndexs[i + k] += table.Items[i].Cells[j].ColumnSpan;
                                    }
                                }
                            }
                            break;
                        case EnmTableBindType.Column:
                            {
                                table.Items[i].Cells[j].Size = new SizeF(
                                    GetSum(itemSizes, i, i + table.Items[i].Cells[j].ColumnSpan - 1),
                                    GetSum(cellSizes, cellIndexs[i], cellIndexs[i] + table.Items[i].Cells[j].RowSpan - 1));
                                table.Items[i].Cells[j].Location = new PointF(
                                    GetSum(itemSizes, 0, i - 1),
                                    GetSum(cellSizes, 0, cellIndexs[i] - 1));
                                table.Items[i].Cells[j].Blocks.ForEach(p => p.InheritLocation(table.Position));
                                table.Items[i].Cells[j].Blocks.ForEach(p => p.AutoSizeLocation(table.Items[i].Cells[j].Size, drawer));
                                cellIndexs[i] += table.Items[i].Cells[j].RowSpan;
                                for (int k = 1; k < table.Items[i].Cells[i].ColumnSpan; k++)
                                {
                                    if (i + k < cellIndexs.Length)
                                    {
                                        cellIndexs[i + k] += table.Items[i].Cells[j].RowSpan;
                                    }
                                }
                            }
                            break;
                    }
                }
            }

            for (int i = 0; i < table.Items.Count; i++)
            {
                for (int j = 0; j < table.Items[i].Cells.Count; j++)
                {
                    if (i == 1 && j == 1)
                    {
                    }
                    table.Items[i].Cells[j].CellBlockAlign(drawer);
                }
            }

            switch (table.TableBindType)
            {
                case EnmTableBindType.Row:
                    if (totalCellSize > table.Size.Width)
                    {
                        table.Size = new SizeF(totalCellSize, table.Size.Height);
                    }
                    break;
                case EnmTableBindType.Column:
                    if (totalCellSize > table.Size.Height)
                    {
                        table.Size = new SizeF(table.Size.Width, totalCellSize);
                    }
                    break;
            }
        }

        static private void CellBlockAlign(this ModCell cell, Drawer drawer)
        {
            if (cell.Position == EnmPosition.Relative)
            {
                BlockRegion blockRegion = new BlockRegion(cell.Size);
                cell.Blocks.ForEach(
                    (p) =>
                    {
                        if (p.BlockType == EnmBlockType.Delimiter)
                        {
                            p.Location = blockRegion.GetFitPosition(p.Location,
                                drawer.GetLineSize(p.Location, PointF.Add(p.Location, p.Size), (p as ModDelimiter).LineStyle),
                                p.Interval);
                        }
                        else
                        {
                            p.Location = blockRegion.GetFitPosition(p.Location, p.Size, p.Interval);
                        }
                    });
            }
            GraphicsPath path = new GraphicsPath();
            cell.Blocks.ForEach(p => path.AddRectangle(p.GetBounds()));
            RectangleF rect = path.GetBounds();
            float horizontalMove = 0;
            float verticalMove = 0;
            switch (cell.HorizontalAlign)
            {
                case EnmHorizontalAlign.Center:
                    horizontalMove = (cell.Size.Width - rect.Width) / 2;
                    break;
                case EnmHorizontalAlign.Right:
                    horizontalMove = cell.Size.Width - rect.Width;
                    break;
            }
            switch (cell.VerticalAlign)
            {
                case EnmVerticalAlign.Middle:
                    verticalMove = (cell.Size.Height - rect.Height) / 2;
                    break;
                case EnmVerticalAlign.Bottom:
                    verticalMove = cell.Size.Height - rect.Height;
                    break;
            }
            cell.Blocks.ForEach(p => p.Location = new PointF(p.Location.X + horizontalMove, p.Location.Y + verticalMove));
        }

        static private float GetSum(float[] array, int begin, int end)
        {
            return array.Where((p, q) => q >= begin && q <= end).Sum();
        }

        static public void Print(this ModTable table, Drawer drawer, PointF location)
        {
            PointF myLocation = new PointF(location.X + table.Location.X, location.Y + table.Location.Y);
            drawer.DrawBackAndBorder(myLocation, table.Size, table.BackgroundStyle, table.BorderStyle);

            table.Items.ForEach(p =>
            {
                p.Cells.ForEach(q =>
                {
                    PointF cellLocation = new PointF(myLocation.X + q.Location.X, myLocation.Y + q.Location.Y);
                    drawer.DrawBackAndBorder(cellLocation, q.Size, q.BackgroundStyle, q.BorderStyle);
                    q.Blocks.ForEach(
                        r => r.Print(drawer, PointF.Add(cellLocation, new SizeF(q.Interval, q.Interval))));
                });
            });
        }
    }
    class TableCount
    {
        public int RowCount
        {
            set;
            get;
        }
        public int ColumnCount
        {
            set;
            get;
        }
    }
}